﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Caching;
using BaoJianSoft.Common.Data;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.ExtensionModel;
using BaoJianSoft.Platform.Properties;

namespace BaoJianSoft.Platform.Linq
{
	/// <summary>
	/// The hierarchy API is used for CRUD generic data within hierarchy using linq-2-sql.
	/// The common business scenario likes Geography that each area in geography potentially has a parent and multiple children.
	/// The example likes the managed geography can be selected as shipping destination city when the user creates a order for shipping in product-order system.
	/// In this case we don't need to design the database schema and create API to CRUD areas in geography any more with Hierarchy model provided in the RapidWebDev. 
	/// We can use IHierarchyApi to CRUD any hierarchy data generally. 
	/// The hierarchy data object focused in the IHierarchyApi includes basic information likes Code, Name, Description, HierarchyType and it integrates the extension model provided in RapidWebDev. 
	/// The property HierarchyType is used to categorize the hierarchy data which provides the capacity to manage multiple hierarchy objects in a system at a time, likes Geography, Functional Zone.
	/// With extension model integrated into Hierarchy, we can define the dynamic properties for hierarchy data based on the needs of business. 
	/// </summary>
	public class HierarchyApi : CachableApi, IHierarchyApi
	{
		private IAuthenticationContext authenticationContext;

		/// <summary>
		/// Construct HierarchyApi
		/// </summary>
		/// <param name="authenticationContext"></param>
		public HierarchyApi(IAuthenticationContext authenticationContext)
		{
			this.authenticationContext = authenticationContext;
		}

		/// <summary>
		/// Save a hierarchy data object.
		/// </summary>
		/// <param name="hierarchyDataObject"></param>
		public void Save(HierarchyDataObject hierarchyDataObject)
		{
			Kit.NotNull(hierarchyDataObject, "hierarchyDataObject");
			Kit.NotNull(hierarchyDataObject.Name, "hierarchyDataObject.Name");
			Kit.NotNull(hierarchyDataObject.HierarchyType, "hierarchyDataObject.HierarchyType");

			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				using (ValidationScope validationScope = new ValidationScope(true))
				{
					// check whether the hierarchy type of parent equals to current one's
					if (hierarchyDataObject.ParentHierarchyDataId.HasValue)
					{
						HierarchyDataObject parentHierarchyDataObject = this.GetHierarchyData(hierarchyDataObject.ParentHierarchyDataId.Value);
						if (parentHierarchyDataObject == null)
							validationScope.Error(Resources.InvalidParentHierarchyDataId);
					}

					// whether to check cycle reference on parent hierarchy data id? 
					// TODO:

					// check duplicate hierarchy data name in a hierarchy type.
					HierarchyDataObject duplicateHierarchyDataObject = this.GetHierarchyData(hierarchyDataObject.HierarchyType, hierarchyDataObject.Name);
					if (duplicateHierarchyDataObject != null && duplicateHierarchyDataObject.Id != hierarchyDataObject.Id)
						validationScope.Error(Resources.DuplicateHierarchyDataName);

					// check whether the hierarchy type has been changed.
					if (hierarchyDataObject.Id != Guid.Empty)
					{
						var existedHierarchyDataObject = this.GetHierarchyData(hierarchyDataObject.Id);
						if (existedHierarchyDataObject == null)
							validationScope.Error(Resources.InvalidHierarchyDataId);

						if (existedHierarchyDataObject.HierarchyType != hierarchyDataObject.HierarchyType)
							validationScope.Error(Resources.HierarchyTypeCannotBeChanged);
					}
				}

				HierarchyData hierarchyData = null;
				if (hierarchyDataObject.Id == Guid.Empty)
				{
					hierarchyData = ExtensionObjectFactory.Create<HierarchyData>(hierarchyDataObject);
					hierarchyData.ApplicationId = this.authenticationContext.ApplicationId;
					hierarchyData.CreatedBy = this.authenticationContext.User.UserId;
					hierarchyData.CreatedDate = DateTime.Now;

					ctx.HierarchyDatas.InsertOnSubmit(hierarchyData);
				}
				else
				{
					hierarchyData = ctx.HierarchyDatas.FirstOrDefault(d => d.HierarchyDataId == hierarchyDataObject.Id);
				}

				hierarchyData.HierachyType = hierarchyDataObject.HierarchyType;
				hierarchyData.ParentHierarchyDataId = hierarchyDataObject.ParentHierarchyDataId;
				hierarchyData.Code = hierarchyDataObject.Code;
				hierarchyData.Name = hierarchyDataObject.Name;
				hierarchyData.Description = hierarchyDataObject.Description;
				hierarchyData.LastUpdatedBy = this.authenticationContext.User.UserId;
				hierarchyData.LastUpdatedDate = DateTime.Now;

				hierarchyData.ParseExtensionPropertiesFrom(hierarchyDataObject);
				ctx.SubmitChanges();

				hierarchyDataObject.Id = hierarchyData.HierarchyDataId;
				hierarchyDataObject.CreatedBy = hierarchyData.CreatedBy;
				hierarchyDataObject.CreatedDate = hierarchyData.CreatedDate;
				hierarchyDataObject.LastUpdatedBy = hierarchyData.LastUpdatedBy;
				hierarchyDataObject.LastUpdatedDate = hierarchyData.LastUpdatedDate;

				base.AddCache(hierarchyDataObject.Id, hierarchyDataObject);
				base.RemoveCache(hierarchyData.HierachyType);
			}
		}

		/// <summary>
		/// Get a hierarchy data object by id.
		/// </summary>
		/// <param name="hierarchyDataId"></param>
		/// <returns></returns>
		public HierarchyDataObject GetHierarchyData(Guid hierarchyDataId)
		{
			HierarchyDataObject hierarchyDataObject = base.GetCacheObject<HierarchyDataObject>(hierarchyDataId);
			if (hierarchyDataObject != null)
				return hierarchyDataObject.Clone();

			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				HierarchyData entity = (from hd in ctx.HierarchyDatas
										where hd.ApplicationId == this.authenticationContext.ApplicationId && hd.HierarchyDataId == hierarchyDataId
										select hd).FirstOrDefault();

				hierarchyDataObject = new HierarchyDataObject
				{
					Id = entity.HierarchyDataId,
					HierarchyType = entity.HierachyType,
					Code = entity.Code,
					Name = entity.Name,
					Description = entity.Description,
					ParentHierarchyDataId = entity.ParentHierarchyDataId,
					CreatedBy = entity.CreatedBy,
					CreatedDate = entity.CreatedDate,
					ExtensionDataTypeId = entity.ExtensionDataTypeId,
					LastUpdatedBy = entity.LastUpdatedBy,
					LastUpdatedDate = entity.LastUpdatedDate
				};

				hierarchyDataObject.ParseExtensionPropertiesFrom(entity);
				base.AddCache(hierarchyDataObject.Id, hierarchyDataObject);
			}

			return hierarchyDataObject;
		}

		/// <summary>
		/// Get a hierarchy data object by name.
		/// </summary>
		/// <param name="hierarchyType"></param>
		/// <param name="hierarchyDataName"></param>
		/// <returns></returns>
		public HierarchyDataObject GetHierarchyData(string hierarchyType, string hierarchyDataName)
		{
			IEnumerable<HierarchyDataObject> allHierarchyData = this.GetAllHierarchyData(hierarchyType);
			var returnValue = allHierarchyData.FirstOrDefault(d => d.Name == hierarchyDataName);
			if (returnValue == null) return null;
			return returnValue.Clone();
		}

		/// <summary>
		/// Get all children of the specified hierarchy data.
		/// </summary>
		/// <param name="hierarchyType"></param>
		/// <param name="parentHierarchyDataId"></param>
		/// <returns></returns>
		public IEnumerable<HierarchyDataObject> GetImmediateChildren(string hierarchyType, Guid? parentHierarchyDataId)
		{
			IEnumerable<HierarchyDataObject> allHierarchyData = this.GetAllHierarchyData(hierarchyType);
			return allHierarchyData.Where(d => d.ParentHierarchyDataId == parentHierarchyDataId).Select(d => d.Clone());
		}

		/// <summary>
		/// Get all children of the specified hierarchy data includes not immediately.
		/// </summary>
		/// <param name="hierarchyType"></param>
		/// <param name="parentHierarchyDataId"></param>
		/// <returns></returns>
		public IEnumerable<HierarchyDataObject> GetAllChildren(string hierarchyType, Guid? parentHierarchyDataId)
		{
			IEnumerable<HierarchyDataObject> allHierarchyData = this.GetAllHierarchyData(hierarchyType);
			List<HierarchyDataObject> results = new List<HierarchyDataObject>();
			RecursivelyCollectChildren(parentHierarchyDataId, allHierarchyData, results);
			return results.Select(d => d.Clone());
		}

		/// <summary>
		/// Get all hierarchy data in specified hierarchy type.
		/// </summary>
		/// <param name="hierarchyType"></param>
		/// <returns></returns>
		public IEnumerable<HierarchyDataObject> GetAllHierarchyData(string hierarchyType)
		{
			Kit.NotNull(hierarchyType, "hierarchyType");
			return this.GetAllHierarchyDataInternal(hierarchyType).Select(h => h.Clone());
		}

		/// <summary>
		/// Delete a hierarchy data with all its children by id. 
		/// </summary>
		/// <param name="hierarchyDataId"></param>
		public void DeleteHierarchyData(Guid hierarchyDataId)
		{
			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				HierarchyData hierarchyData = ctx.HierarchyDatas.FirstOrDefault(d => d.HierarchyDataId == hierarchyDataId);
				if (hierarchyData == null) return;

				ctx.HierarchyDatas.DeleteOnSubmit(hierarchyData);
				this.RecursivelyDeleteChildHierarchyData(ctx, new[] { hierarchyDataId });

				ctx.SubmitChanges();
				base.RemoveCache(hierarchyData.HierachyType);
			}
		}

		private IEnumerable<HierarchyDataObject> GetAllHierarchyDataInternal(string hierarchyType)
		{
			Kit.NotNull(hierarchyType, "hierarchyType");
			IList<HierarchyDataObject> results = base.GetCacheObject<IList<HierarchyDataObject>>(hierarchyType);
			if (results != null) return results;

			using (MembershipDataContext ctx = DataContextFactory.Create<MembershipDataContext>())
			{
				List<HierarchyData> entities = (from hd in ctx.HierarchyDatas
												where hd.ApplicationId == this.authenticationContext.ApplicationId && hd.HierachyType == hierarchyType
												select hd).ToList();

				results = new List<HierarchyDataObject>();
				foreach (HierarchyData entity in entities)
				{
					HierarchyDataObject hierarchyDataObject = new HierarchyDataObject
					{
						Id = entity.HierarchyDataId,
						HierarchyType = entity.HierachyType,
						Code = entity.Code,
						Name = entity.Name,
						Description = entity.Description,
						ParentHierarchyDataId = entity.ParentHierarchyDataId,
						CreatedBy = entity.CreatedBy,
						CreatedDate = entity.CreatedDate,
						ExtensionDataTypeId = entity.ExtensionDataTypeId,
						LastUpdatedBy = entity.LastUpdatedBy,
						LastUpdatedDate = entity.LastUpdatedDate
					};

					hierarchyDataObject.ParseExtensionPropertiesFrom(entity);

					results.Add(hierarchyDataObject);
					base.AddCache(hierarchyDataObject.Id, hierarchyDataObject);
				}

				base.AddCache(hierarchyType, results);
				return results;
			}
		}

		private void RecursivelyDeleteChildHierarchyData(MembershipDataContext ctx, Guid[] parentHierarchyDataIds)
		{
			if (parentHierarchyDataIds == null || parentHierarchyDataIds.Length == 0) return;

			foreach (Guid parentHierarchyDataId in parentHierarchyDataIds)
				base.RemoveCache(parentHierarchyDataId);

			List<HierarchyData> hierarchyDataList = ctx.HierarchyDatas.Where(d => parentHierarchyDataIds.Contains(d.ParentHierarchyDataId.Value)).ToList();
			ctx.HierarchyDatas.DeleteAllOnSubmit(hierarchyDataList);

			Guid[] hierarchyDataIdArray = hierarchyDataList.Select(d => d.HierarchyDataId).ToArray();
			RecursivelyDeleteChildHierarchyData(ctx, hierarchyDataIdArray);
		}

		private static void RecursivelyCollectChildren(Guid? parentHierarchyDataId, IEnumerable<HierarchyDataObject> allHierarchyData, List<HierarchyDataObject> results)
		{
			List<HierarchyDataObject> immediateChildren = allHierarchyData.Where(d => d.ParentHierarchyDataId == parentHierarchyDataId).ToList();
			if (immediateChildren.Count() == 0) return;

			foreach (HierarchyDataObject immediateChild in immediateChildren)
			{
				results.Add(immediateChild);
				RecursivelyCollectChildren(immediateChild.Id, allHierarchyData, results);
			}
		}
	}
}
